
#include "desktopsource.h"
#include "persistence/settings.h"
#include "../platform/system_version.h"
#include "../platform/system_error.h"
#include "record_desktop_gdi.h"
#include "record_desktop_duplication.h"

#include "error_define.h"

#include <vpx/vpx_codec.h>
#include <vpx/vpx_image.h>
#include <functional>
#include <memory>
#include <iostream>
#include <shared_mutex>

#ifdef  _WIN32
#include <Windows.h>
#elif __linux__ && !__ANDROID__
#include <X11/Xlib.h>
#include "record_desktop_x11.h"
#endif

typedef enum {
    DT_DESKTOP_NO = 0,
    DT_DESKTOP_FFMPEG_GDI,
    DT_DESKTOP_WIN_GDI,
    DT_DESKTOP_WIN_DUPLICATION,
    DT_DESKTOP_LINUX_X11
}RECORD_DESKTOP_TYPES;

int record_desktop_new(RECORD_DESKTOP_TYPES type, record_desktop** recorder)
{
    int err = AE_NO;
    switch (type)
    {
#if defined (_WIN32)
    case DT_DESKTOP_WIN_GDI:
        *recorder = (record_desktop*)new record_desktop_gdi();
        break;
    case DT_DESKTOP_WIN_DUPLICATION:
        *recorder = (record_desktop*)new record_desktop_duplication();
        break;
#elif defined (__linux__)  && !defined(__ANDROID__)
    case DT_DESKTOP_LINUX_X11:
        *recorder = (record_desktop*)new record_desktop_x11();
        break;
#endif
    default:
        err = AE_UNSUPPORT;
        break;
    }

    return err;
}

void record_desktop_destroy(record_desktop** recorder)
{
    if (*recorder != nullptr) {
        (*recorder)->stop();

        delete* recorder;
    }

    *recorder = nullptr;
}


DesktopSource* DesktopSource::instance{nullptr};

DesktopSource::DesktopSource()
    :  deviceName{"none"}
    //, device{nullptr}
    , mode(VideoMode())
    //, cctx{nullptr}
    , videoStreamIndex{-1}
    , _isNone{true}
    , subscriptions{0}
{
    subscriptions = 0;
}

// clang-format on

/**
 * @brief Returns the singleton instance.
 */
DesktopSource& DesktopSource::getInstance()
{
    if (!instance)
        instance = new DesktopSource();
    return *instance;
}

void DesktopSource::destroyInstance()
{
    delete instance;
    instance = nullptr;
}


#ifdef _WIN32
BOOL CALLBACK EnumMonitor(HMONITOR handle, HDC, LPRECT rect, LPARAM param) {
    std::vector<MONITORINFO>* list = (std::vector<MONITORINFO>*)param;
    MONITORINFOEX  mi;
    mi.cbSize = sizeof(mi);
    GetMonitorInfo(handle, &mi);
    //HDC dc = CreateDC(NULL, mi.szDevice, NULL, NULL);
    //HDC hdc = CreateDC(mi.szDevice, nullptr, nullptr, nullptr);
    //int w1 = GetDeviceCaps(dc, DESKTOPHORZRES);
    //int h1 = GetDeviceCaps(dc, DESKTOPVERTRES);
    //DeleteDC(dc);

    //mi.rcMonitor.right = w1> mi.rcMonitor.right?w1:mi.rcMonitor.right;
    //mi.rcMonitor.bottom = h1 > mi.rcMonitor.bottom ? h1 : mi.rcMonitor.bottom;
    std::cout << "xy:" << mi.rcMonitor.left << "," << mi.rcMonitor.top << ",rb:"
        << mi.rcMonitor.right << "," << mi.rcMonitor.bottom << ",is PRIMARY:" << mi.dwFlags << std::endl;
    list->push_back(mi);
    return true;
}
#endif // _WIN32


std::vector<VideoMode> DesktopSource::getScreenModes()
{
    std::vector<VideoMode> result;
#ifdef  _WIN32
    //int screenNum = GetSystemMetrics(SM_CMONITORS);
    std::vector<MONITORINFO> monitorlist;
    EnumDisplayMonitors(NULL, NULL, EnumMonitor, (LPARAM)&monitorlist);
    for (size_t i = 0; i < monitorlist.size(); ++i) {

        VideoMode mode(monitorlist[i].rcMonitor.right - monitorlist[i].rcMonitor.left,
            monitorlist[i].rcMonitor.bottom - monitorlist[i].rcMonitor.top,
            0, 0, 20);
        result.push_back(mode);
    }
#endif //  _WIN32
#if defined (__linux__)  && !defined(__ANDROID__)
//todo frirt desktop
    Display* deskdisplay = XOpenDisplay(NULL);
    int deskscreen  = DefaultScreen(deskdisplay);
    int width = XDisplayWidth(deskdisplay,deskscreen);
    int hight = XDisplayHeight(deskdisplay,deskscreen);
    VideoMode mode(width,hight, 0, 0, 20);
    result.push_back(mode);
    XCloseDisplay(deskdisplay);
#endif

    return result;
}


/**
 * @brief Change the device and mode.
 * @note If a device is already open, the source will seamlessly switch to the new device.
 */
void DesktopSource::setupDevice(const std::string& DeviceName,  VideoMode& Mode)
{
    //Mode.x += Mode.width;
    //Mode.width = Mode.width / 2;
    //Mode.height = Mode.height / 2;
    //Mode.FPS = 1;
    std::lock_guard locker{deviceMutex};

    if (DeviceName == deviceName && Mode == mode) {
        return;
    }

    if (subscriptions) {
        // To force close, ignoring optimization
        int subs = subscriptions;
        subscriptions = 0;
        //closeDevice();
        subscriptions = subs;
    }

#ifdef _WIN32
    if (system_version::is_win8_or_above()) {
        int error = record_desktop_new(RECORD_DESKTOP_TYPES::DT_DESKTOP_WIN_DUPLICATION, &_recorder_desktop);
        if (error == AE_NO) {

            error = _recorder_desktop->init(Mode.toRect(), Mode.FPS );

            if (error != AE_NO)
                record_desktop_destroy(&_recorder_desktop);
        }
    }

    if (_recorder_desktop == nullptr) {
        int error = record_desktop_new(RECORD_DESKTOP_TYPES::DT_DESKTOP_WIN_GDI, &_recorder_desktop);
        if (error == AE_NO) {

            error = _recorder_desktop->init(Mode.toRect(), Mode.FPS);
        }
        if (error != AE_NO)
        {
            return;
        }
    }
#elif __linux__
    int error = record_desktop_new(RECORD_DESKTOP_TYPES::DT_DESKTOP_LINUX_X11,&_recorder_desktop);
    if (error == AE_NO) {

        error = _recorder_desktop->init(Mode.toRect(), Mode.FPS);
    }
    if (error != AE_NO)
    {
        return;
    }
#endif // _WIN32
    _recorder_desktop->_on_data.connect(std::bind(&DesktopSource::on_desktop_data, this, std::placeholders::_1));

    _recorder_desktop->start();

    deviceName = DeviceName;
    mode = Mode;
    _isNone = (deviceName == "none");

    if (subscriptions && !_isNone) {
        //openDevice();
    }
}

void DesktopSource::on_desktop_data(FRAME_PKG* vframe)
{
    std::shared_ptr<vpx_image_t> input(new vpx_image_t,[](vpx_image_t* ptr) {
        vpx_img_free(ptr);
        delete(ptr);
    });
    vpx_img_alloc(input.get(), VPX_IMG_FMT_I420, vframe->w, vframe->h, 1);

    if (!input) {
        return;
    };

    bgrxtoyuv420(input->planes[0], input->planes[1], input->planes[2],(uint8_t*)vframe->img, vframe->w, vframe->h);
    frameAvailable(input);
}


bool DesktopSource::isNone() const
{
    return _isNone;
}

DesktopSource::~DesktopSource()
{
    std::unique_lock streamlocker{streamMutex};
    std::unique_lock devicelocker{deviceMutex};

    if (_isNone) {
        return;
    }

    streamlocker.unlock();
    devicelocker.unlock();
   // streamFuture.wait();
}

void DesktopSource::subscribe()
{
    std::lock_guard locker{deviceMutex};

    ++subscriptions;
    //openDevice();
}

void DesktopSource::unsubscribe()
{
    std::lock_guard locker{ deviceMutex };

    --subscriptions;
    if (subscriptions == 0) {
        //closeDevice();
    }
}

